package com.css.dbshards;


/**
 * @author 郭辉
 * @className TODO
 * @description TODO
 * @date 2022/11/28 20:23
 * @company 海康威视
 * @since 1.0.0
 */
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.Properties;

/**
 * MybatisSubmeterInterceptor
 * @Description: 分表拦截器
 * @Author:
 * @Date 2022/7/29 9:43
 * @Version 1.0.0
 */
@ConditionalOnClass(value = org.apache.ibatis.plugin.Interceptor.class)
@Component("mybatisSubmeterInterceptor")
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Order(0)
@Slf4j
public class MybatisSubmeterInterceptor  implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        Executor executor = (Executor) invocation.getTarget();
        // 获取原始的sql语句
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1];
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        String oldsql = boundSql.getSql();

        // 判断是否需要分表
        if (isReplaceTableName(oldsql)) {
            log.error("\n分表前的sql：{}", oldsql);

            // 查询单独处理
            if (mappedStatement.getSqlCommandType().compareTo(SqlCommandType.SELECT) == 0) {
                RowBounds rowBounds = (RowBounds) args[2];
                ResultHandler resultHandler = (ResultHandler) args[3];

                CacheKey cacheKey;
                BoundSql boundSqlnew;
                if (args.length == 4) {
                    boundSqlnew = mappedStatement.getBoundSql(parameter);
                    cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, boundSqlnew);
                } else {
                    boundSqlnew = (BoundSql) args[5];
                    cacheKey = (CacheKey) args[4];
                }

                String sql = boundSqlnew.getSql();
                String newsql = sql.replace("db_father", "db_father_" + "2400");
                String newsql2 = sql.replace("DB_FATHER", "DB_FATHER_" + "2400");
                newsql = StringUtils.pathEquals(sql,newsql) ? newsql2 : newsql;
                //通过反射修改sql语句
                Field field = boundSqlnew.getClass().getDeclaredField("sql");
                field.setAccessible(true);
                field.set(boundSqlnew, newsql);

                log.error("\n分表后的sql：{}", boundSqlnew.getSql());
                // 直接query可能会应该其他拦截器
                return executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSqlnew);
            } else {
                // 分表策略，这个地方可以使用注解，配置类，配置数据库等方式
                String newsql = oldsql.replace("db_father", "db_father_" + "2400");
                String newsql2 = oldsql.replace("DB_FATHER", "DB_FATHER_" + "2400");
                newsql = StringUtils.pathEquals(oldsql,newsql) ? newsql2 : newsql;

                //重新生成一个BoundSql对象
                BoundSql bs = new BoundSql(mappedStatement.getConfiguration(),newsql,boundSql.getParameterMappings(),parameter);

                //重新生成一个MappedStatement对象
                MappedStatement newMappedStatement = copyMappedStatement(mappedStatement, new BoundSqlSqlSource(bs));
                //赋回给实际执行方法所需的参数中
                args[0] = newMappedStatement;
            }
            log.error("\n分表后的sql：{}", ((MappedStatement)args[0]).getBoundSql(parameter).getSql());
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {
        log.info("setProperties " + properties.toString());
    }

    /**
     * MybatisSubmeterInterceptor.java
     *
     * @Description: 判断是否需要分表
     * @Author:
     * @Date 2022/7/29 11:53
     * @param sql sql
     * @return boolean
     * @Version 1.0.0
     **/
    private boolean isReplaceTableName(String sql) {
        // 测试数据，将ddb_father  重新定位到db_father_2400,
        // 这个地方可以使用注解，配置类，配置数据库等方式判断
        if (sql.contains("db_father") || sql.contains("DB_FATHER")) {
            return true;
        }
        return false;
    }

    /**
     * MybatisSubmeterInterceptor.java
     *
     * @Description: 生成一个新的mappedStatement
     * @Date 2022/7/29 13:49
     * @param ms
     * @param newSqlSource
     * @return org.apache.ibatis.mapping.MappedStatement
     * @Version 1.0.0
     **/
    private MappedStatement copyMappedStatement (MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());

        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(String.join(",",ms.getKeyProperties()));
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

    /***
     * MappedStatement构造器接受的是SqlSource
     * 实现SqlSource接口，将BoundSql封装进去
     */
    public static class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;
        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }
        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }

}

